package movement

import (
	b "code.google.com/p/gochess/board"
	pos "code.google.com/p/gochess/position"
)

// todo: promotions?
// todo: capturing promotions?
func mvvLvaSortValue(victim, attacker pos.PieceType) int32 {
	return int32(victim)<<pos.BitSizePieceType - int32(attacker)
}

// generates all piece captures by walking along the rays
func GeneratePieceCaptures(p *pos.Position, moves *Movelist) {
	Stats.GeneratePieceCaptures++
	piece := pos.NewPiece(p.SideToMove(), pos.Knight)
	opponent := pos.Opponent(p.SideToMove())
	for attacker := pos.Knight; attacker <= pos.King; attacker++ {
		if movements[attacker].IsSlider {
			var sq b.Square
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, dir := range movements[attacker].Directions {
					for sq = start + dir; p.Get(sq) == pos.NoPiece; sq += dir {
					}
					if p.Get(sq).Color() == opponent {
						moves.Append(pos.NewMove(start, sq),
							mvvLvaSortValue(p.Get(sq).Type(), attacker))
					}
				}
			}
		} else {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, dir := range movements[attacker].Directions {
					if p.Get(start+dir).Color() == opponent {
						moves.Append(pos.NewMove(start, start+dir),
							mvvLvaSortValue(p.Get(start+dir).Type(), attacker))
					}
				}
			}
		}
		piece = piece.Inc()
	}
}

// generate captures by pairing opponents
func generatePieceCapturesPieceWise(p *pos.Position, captures *Movelist) {

	opponent := pos.Opponent(p.SideToMove())
	piece := pos.NewPiece(p.SideToMove(), pos.Knight)
	for attacker := pos.Knight; attacker <= pos.King; attacker++ {
		if movements[attacker].IsSlider {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, direction := range movements[attacker].Directions {
					square := start + direction
					for ; p.Get(square) == pos.NoPiece; square += direction {
					}
					if p.Get(square).Color() == opponent {
						captures.Append(pos.NewMove(start, square),
							mvvLvaSortValue(p.Get(square).Type(), attacker))
					}
				}
			}
		} else {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, direction := range movements[attacker].Directions {
					if p.Get(start+direction).Color() == opponent {
						captures.Append(pos.NewMove(start, start+direction),
							mvvLvaSortValue(p.Get(start+direction).Type(), attacker))
					}
				}
			}
		}
		piece = piece.Inc()
	}
}

// BLIND (Better or Lower If Not Defended) piece captures
func generateBlindPieceCaptures(p *pos.Position, captures *Movelist) {

	opponent := pos.Opponent(p.SideToMove())
	for attacker := pos.Knight; attacker <= pos.King; attacker++ {
		pieces := p.PieceList(pos.NewPiece(p.SideToMove(), attacker))
		if movements[attacker].IsSlider {
			for i := pieces.Max - 1; i >= 0; i-- {
				start := pieces.Pieces[i]
				for _, direction := range movements[attacker].Directions {
					square := start + direction
					for ; p.Get(square) == pos.NoPiece; square += direction {
					}
					if p.Get(square).Color() == opponent &&
						(attacker <= p.Get(square).Type() ||
							!UnderAttack(p, square, opponent)) {
						captures.Append(pos.NewMove(start, square),
							mvvLvaSortValue(p.Get(square).Type(), attacker))
					}
				}
			}
		} else {
			for i := pieces.Max - 1; i >= 0; i-- {
				start := pieces.Pieces[i]
				for _, direction := range movements[attacker].Directions {
					if p.Get(start+direction).Color() == opponent &&
						(attacker <= p.Get(start+direction).Type() ||
							!UnderAttack(p, start+direction, opponent)) {
						captures.Append(pos.NewMove(start, start+direction),
							mvvLvaSortValue(p.Get(start+direction).Type(), attacker))
					}
				}
			}
		}
	}
}

// generate all pawn captures including promotions
// todo: capture sort values for promotions
// right now we are ignoring promotions completely, which is bad
func GeneratePawnCapturesAndPromotions(p *pos.Position, moves *Movelist) {
	Stats.GeneratePawnCapturesAndPromotions++

	forward := pos.MoveDirection(p.SideToMove())
	pawn := pos.NewPiece(p.SideToMove(), pos.Pawn)
	// en passant capture
	if p.EnPassantTarget() != b.NoSquare {
		epTarget := p.EnPassantTarget()
		if p.Get(epTarget-forward+b.Left) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Left, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
		if p.Get(epTarget-forward+b.Right) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Right, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
	}
	// pawn captures and promotions
	opponent := pos.Opponent(p.SideToMove())
	for i := p.PieceList(pawn).Max - 1; i >= 0; i-- {
		start := p.PieceList(pawn).Pieces[i]
		if b.GetRank(start) == pos.PromotionRank(p.SideToMove()) {
			dst := start + forward + b.Left
			if p.Get(dst).Color() == opponent {
				// promotion capture
				for promoteTo := pos.Queen; promoteTo != pos.Pawn; promoteTo-- {
					moves.Append(pos.NewPromotion(start, dst, promoteTo),
						mvvLvaSortValue(promoteTo, pos.Pawn))
				}
			}
			dst = start + forward + b.Right
			if p.Get(dst).Color() == opponent {
				// promotion capture
				for promoteTo := pos.Queen; promoteTo != pos.Pawn; promoteTo-- {
					moves.Append(pos.NewPromotion(start, dst, promoteTo),
						mvvLvaSortValue(promoteTo, pos.Pawn))
				}
			}
			if p.Get(start+forward) == pos.NoPiece {
				// promotion push
				for promoteTo := pos.Queen; promoteTo != pos.Pawn; promoteTo-- {
					moves.Append(pos.NewPromotion(start, start+forward, promoteTo),
						mvvLvaSortValue(promoteTo, pos.Pawn))
				}
			}
		} else {
			// no promotion
			dst := start + forward + b.Left
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
			dst = start + forward + b.Right
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
		}
	}
}

func GeneratePawnCapturesAndQueenPromotions(p *pos.Position, moves *Movelist) {

	Stats.GeneratePawnCapturesAndQueenPromotions++
	forward := pos.MoveDirection(p.SideToMove())
	pawn := pos.NewPiece(p.SideToMove(), pos.Pawn)
	// en passant capture
	if p.EnPassantTarget() != b.NoSquare {
		epTarget := p.EnPassantTarget()
		if p.Get(epTarget-forward+b.Left) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Left, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
		if p.Get(epTarget-forward+b.Right) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Right, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
	}
	// pawn captures and promotions
	opponent := pos.Opponent(p.SideToMove())
	mvvLvaValue := mvvLvaSortValue(pos.Queen, pos.Pawn)
	for i := p.PieceList(pawn).Max - 1; i >= 0; i-- {
		start := p.PieceList(pawn).Pieces[i]
		if b.GetRank(start) == pos.PromotionRank(p.SideToMove()) {
			dst := start + forward + b.Left
			if p.Get(dst).Color() == opponent {
				// promotion capture
				moves.Append(pos.NewPromotion(start, dst, pos.Queen), mvvLvaValue)
			}
			dst = start + forward + b.Right
			if p.Get(dst).Color() == opponent {
				// promotion capture
				moves.Append(pos.NewPromotion(start, dst, pos.Queen), mvvLvaValue)
			}
			if p.Get(start+forward) == pos.NoPiece {
				// promotion push
				moves.Append(pos.NewPromotion(start, start+forward, pos.Queen), mvvLvaValue)
			}
		} else {
			// no promotion
			dst := start + forward + b.Left
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
			dst = start + forward + b.Right
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
		}
	}
}

// pawn captures excluding promotions
// not used
func generatePawnCaptures(p *pos.Position, moves *Movelist) {

	forward := pos.MoveDirection(p.SideToMove())
	pawn := pos.NewPiece(p.SideToMove(), pos.Pawn)
	// en passant capture
	if p.EnPassantTarget() != b.NoSquare {
		epTarget := p.EnPassantTarget()
		if p.Get(epTarget-forward+b.Left) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Left, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
		if p.Get(epTarget-forward+b.Right) == pawn {
			moves.Append(pos.NewEnPassant(epTarget-forward+b.Right, epTarget),
				mvvLvaSortValue(pos.Pawn, pos.Pawn))
		}
	}
	opponent := pos.Opponent(p.SideToMove())
	for i := p.PieceList(pawn).Max - 1; i >= 0; i-- {
		start := p.PieceList(pawn).Pieces[i]
		if b.GetRank(start) != pos.PromotionRank(p.SideToMove()) {
			// no promotion
			dst := start + forward + b.Left
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
			dst = start + forward + b.Right
			if p.Get(dst).Color() == opponent {
				moves.Append(pos.NewMove(start, dst),
					mvvLvaSortValue(p.Get(dst).Type(), pos.Pawn))
			}
		}
	}
}

// generates all moves excluding pawn moves
func GeneratePieceMoves(p *pos.Position, moves *Movelist) {
	Stats.GeneratePieceMoves++
	piece := pos.NewPiece(p.SideToMove(), pos.Knight)
	var sq b.Square
	for mover := pos.Knight; mover <= pos.King; mover++ {
		if movements[mover].IsSlider {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, dir := range movements[mover].Directions {
					for sq = start + dir; p.Get(sq) == pos.NoPiece; sq += dir {
						moves.Append(pos.NewMove(start, sq),
							PositionBonus(piece, sq)-PositionBonus(piece, start))
					}
				}
			}
		} else {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, dir := range movements[mover].Directions {
					if p.Get(start+dir) == pos.NoPiece {
						moves.Append(pos.NewMove(start, start+dir),
							PositionBonus(piece, start+dir)-PositionBonus(piece, start))
					}
				}
			}
		}
		piece = piece.Inc()
	}
}

// generates all moves excluding pawn moves
// this function is not used
func generatePieceMovesAndCaptures(p *pos.Position, moves, captures *Movelist) {

	opponent := pos.Opponent(p.SideToMove())
	for pieceType := pos.Knight; pieceType <= pos.King; pieceType++ {
		piece := pos.NewPiece(p.SideToMove(), pieceType)
		if movements[pieceType].IsSlider {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, direction := range movements[pieceType].Directions {
					square := start + direction
					for ; p.Get(square) == pos.NoPiece; square += direction {
						moves.Append(pos.NewMove(start, square),
							PositionBonus(piece, square)-PositionBonus(piece, start))
					}
					if p.Get(square).Color() == opponent {
						captures.Append(pos.NewMove(start, square),
							mvvLvaSortValue(p.Get(square).Type(), pieceType))
					}
				}
			}
		} else {
			for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
				start := p.PieceList(piece).Pieces[i]
				for _, direction := range movements[pieceType].Directions {
					if p.Get(start+direction) == pos.NoPiece {
						moves.Append(pos.NewMove(start, start+direction),
							PositionBonus(piece, start+direction)-PositionBonus(piece, start))
						continue
					}
					if p.Get(start+direction).Color() == opponent {
						captures.Append(pos.NewMove(start, start+direction),
							mvvLvaSortValue(p.Get(start+direction).Type(), pieceType))

					}
				}
			}
		}
	}
}

// GeneratePawnMoves generates all pawn moves, excluding captures and promotions and
// adds them to moves.
func GeneratePawnMoves(p *pos.Position, moves *Movelist) {
	Stats.GeneratePawnMoves++
	pawn := pos.NewPiece(p.SideToMove(), pos.Pawn)
	forward := pos.MoveDirection(p.SideToMove())

	for i := p.PieceList(pawn).Max - 1; i >= 0; i-- {
		start := p.PieceList(pawn).Pieces[i]
		if p.Get(start+forward) == pos.NoPiece &&
			b.GetRank(start) != pos.PromotionRank(p.SideToMove()) {
			// no promotions here, these are handled somewhere else
			dst := start + forward
			moves.Append(pos.NewMove(start, dst),
				PositionBonus(pawn, dst)-PositionBonus(pawn, start))
			if b.GetRank(start) == pos.PawnRank(p.SideToMove()) {
				// pawn did not move yet, double push possible
				if p.Get(dst+forward) == pos.NoPiece {
					// both squares in front of the pawns are free -> make double push move
					moves.Append(pos.NewMove(start, dst+forward),
						PositionBonus(pawn, dst+forward)-PositionBonus(pawn, start))
				}
			}
		}
	}
}

// returns true if there is no obstacle wich makes side to move impossible to castle.
// castling rights are not checked here.
// we check if the king is in check here, because we use this function in places
// where we dont check if sideToMove is in check
func castlingTemporaryPossible(p *pos.Position, castlingOption pos.CastlingOption) bool {

	castling := &pos.Castlings[p.SideToMove()][castlingOption]
	// check if there is an occupied square between king and rook
	square := castling.KingLocation + castling.KingMoveDirection
	for ; p.Get(square) == pos.NoPiece && square != castling.RookLocation; square += castling.KingMoveDirection {
	}
	if square != castling.RookLocation {
		return false
	}
	// no place between the king and the rook is occupied
	// make sure that
	// - the king may not pass through a square that is under attack by the opponent
	// - the king is not in check and must not end up in check
	opponent := pos.Opponent(p.SideToMove())
	for square = castling.KingLocation; square != castling.KingDestination &&
		!UnderAttack(p, square, opponent); square += castling.KingMoveDirection {
	}
	return square == castling.KingDestination && !UnderAttack(p, square, opponent)
}

// returns legal moves only. is usable even if side to move is in check
func GenerateCastlings(p *pos.Position, moves *Movelist) {
	Stats.GenerateCastlings++
	if !p.CastlingRights().Possible(p.SideToMove(), pos.CastlingShort|pos.CastlingLong) {
		return
	}
	for option := pos.CastlingShort; option <= pos.CastlingLong; option++ {
		if p.CastlingRights().Possible(p.SideToMove(), option) &&
			castlingTemporaryPossible(p, option) {
			moves.Append(pos.NewCastling(p.SideToMove(), option), 100)
		}
	}
}

func DetectCheck(p *pos.Position) (attacks int32, from b.Square) {
	Stats.DetectCheck++
	attacker := pos.Opponent(p.SideToMove())
	king := p.King(p.SideToMove())
	forward := -pos.MoveDirection(attacker)
	squareUp := king + forward
	chessBids := [2]b.Square{}

	switch pos.NewPiece(attacker, pos.Pawn) {
	case p.Get(squareUp + b.Left):
		chessBids[attacks] = squareUp + b.Left
		attacks++
	case p.Get(squareUp + b.Right):
		chessBids[attacks] = squareUp + b.Right
		attacks++
	}
	piece := pos.NewPiece(attacker, pos.Knight)
	for pt := pos.Knight; pt < pos.King; pt++ {
		for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
			attackerSquare := p.PieceList(piece).Pieces[i]
			if moveInfo(king, attackerSquare).canMove(pt) {
				// attack possible
				direction := moveInfo(king, attackerSquare).Direction()
				square := king + direction
				for ; p.Get(square) == pos.NoPiece && square != attackerSquare; square += direction {
				}
				if square == attackerSquare {
					// no blocker in between or attacker is a non slider
					// -> square is attacked
					chessBids[attacks] = attackerSquare
					attacks++
				}
			}
		}
		piece = piece.Inc()
	}
	// in case of a double check we return the discovered
	// check if the direct attacker is a non slider
	// todo: is this nonsense?
	if attacks == 2 &&
		(p.Get(chessBids[0]).Type() == pos.Pawn || p.Get(chessBids[0]).Type() == pos.Knight) {
		return attacks, chessBids[1]
	}
	return attacks, chessBids[0]
}
func DetectCheckByLastMove(p *pos.Position, oppMove pos.Move) (attacks int32, from b.Square) {
	Stats.DetectCheckByLastMove++
	king := p.King(p.SideToMove())
	start, dst := oppMove.Start(), oppMove.Destination()
	var sq b.Square
	// find direct checks
	if p.Get(dst).Type() == pos.Pawn {
		switch dst {
		case king + pos.MoveDirection(p.SideToMove()) + b.Right:
			attacks, from = attacks+1, dst
		case king + pos.MoveDirection(p.SideToMove()) + b.Left:
			attacks, from = attacks+1, dst
		}
	} else {
		if moveInfo(king, dst).canMove(p.Get(dst).Type()) {
			dir := moveInfo(king, dst).Direction()
			for sq = king + dir; sq != dst && p.Get(sq) == pos.NoPiece; sq += dir {
			}
			if sq == dst {
				attacks, from = attacks+1, dst
			}
		}
	}
	// find discovered check by en passant capture
	// make sure that the ep target is not on the attack ray, as
	// we would count the attack twice
	if oppMove.CapturesEnPassant() &&
		moveInfo(king, start).Direction() !=
			moveInfo(king, dst+pos.MoveDirection(p.SideToMove())).Direction() {
		// start-pos.MoveDirection(p.SideToMove()) is wrong
		// write a test for this case
		info := moveInfo(king, dst+pos.MoveDirection(p.SideToMove()))
		if info.isRay() {
			dir := info.Direction()
			for sq = king + dir; p.Get(sq) == pos.NoPiece; sq += dir {
			}
			if p.Get(sq).Color() == pos.Opponent(p.SideToMove()) &&
				moveInfo(king, sq).canMove(p.Get(sq).Type()) {
				// attack from opponent slider
				attacks, from = attacks+1, sq
			}
		}
	}
	// find discovered check
	if moveInfo(king, start).isRay() {
		// start of move and king are on one ray, find an attacker behind start sq
		dir := moveInfo(king, start).Direction()
		// if attacker walks along attack ray we did find the attack already
		if dir != moveInfo(king, dst).Direction() {
			for sq = king + dir; p.Get(sq) == pos.NoPiece; sq += dir {
			}
			if p.Get(sq).Color() == pos.Opponent(p.SideToMove()) &&
				moveInfo(king, sq).canMove(p.Get(sq).Type()) {
				attacks, from = attacks+1, sq
			}
		}
	}
	// castlings are no special case, as these moves are saved as rook moves.
	return
}

func maxRay(p *pos.Position, color pos.Color) pos.PieceType {
	if p.PieceList(pos.NewPiece(color, pos.Queen)).Max != 0 {
		Stats.MaxRayQueen++
		return pos.Queen
	}
	if p.PieceList(pos.NewPiece(color, pos.Rook)).Max == 0 {
		if p.PieceList(pos.NewPiece(color, pos.Bishop)).Max == 0 {
			Stats.MaxRayNoPieceType++
			return pos.NoPieceType
		}
		Stats.MaxRayBishop++
		return pos.Bishop
	}
	if p.PieceList(pos.NewPiece(color, pos.Bishop)).Max == 0 {
		Stats.MaxRayRook++
		return pos.Rook
	}
	Stats.MaxRayQueen++
	return pos.Queen
}

// GenerateCheckEvasions returns legal moves only.
//
// in this function we should check if sliders etc. are still on board
func GenerateCheckEvasions(p *pos.Position, attacks int32, from b.Square, moves *Movelist) {
	Stats.GenerateCheckEvasions++
	stm := p.SideToMove()
	opponent := pos.Opponent(stm)
	king := p.King(stm)
	ownPawn := pos.NewPiece(stm, pos.Pawn)
	ownKnight := pos.NewPiece(stm, pos.Knight)
	down := -pos.MoveDirection(stm)
	//myVector := maxRay(p, stm)
	//oppVector := maxRay(p, opponent)

	if p.Get(from).Type() == pos.Pawn || p.Get(from).Type() == pos.Knight {
		//
		// non slider attack
		//
		// king evasions incl. captures
		// todo: why not 		if chessBid.numberOfAttacks() == 2 {?
		for _, direction := range movements[pos.King].Directions {
			switch {
			case p.Get(king+direction) == pos.NoPiece &&
				!UnderAttack(p, king+direction, opponent):
				moves.Append(pos.NewMove(king, king+direction),
					mvvLvaSortValue(pos.NoPieceType, pos.King))

			case p.Get(king+direction) != pos.NoPiece &&
				p.Get(king+direction).Color() == opponent &&
				!UnderAttack(p, king+direction, opponent):
				moves.Append(pos.NewMove(king, king+direction),
					mvvLvaSortValue(p.Get(king+direction).Type(), pos.King))
			}
		}
		// slider captures attacker
		for _, direction := range movements[pos.Queen].Directions {
			square := from + direction
			for ; p.Get(square) == pos.NoPiece; square += direction {
			}
			if p.Get(square).Color() == stm &&
				// king moves handled somewhere else
				p.Get(square).Type() != pos.King &&
				// pawn moves handled somewhere else
				p.Get(square).Type() != pos.Pawn &&
				moveInfo(square, from).canMove(p.Get(square).Type()) &&
				!isPinned(p, square, king) {
				moves.Append(pos.NewMove(square, from),
					mvvLvaSortValue(p.Get(from).Type(), p.Get(square).Type()))
			}
		}
		// knight captures attacker
		// todo: check if there are knights
		for _, direction := range movements[pos.Knight].Directions {
			square := from + direction
			if p.Get(square) == ownKnight && !isPinned(p, square, king) {
				moves.Append(pos.NewMove(square, from),
					mvvLvaSortValue(p.Get(from).Type(), pos.Knight))
			}
		}
		// if king is attacked by double pushed pawn, try to capture
		// the attacker en passant
		if p.EnPassantTarget() != b.NoSquare &&
			p.Get(from).Type() == pos.Pawn &&
			p.EnPassantTarget()+down == from {
			// king is attacked by double push pawn
			if p.Get(from+b.Right) == ownPawn &&
				!isPinned(p, from+b.Right, king) {
				moves.Append(pos.NewEnPassant(from+b.Right, p.EnPassantTarget()),
					mvvLvaSortValue(pos.Pawn, pos.Pawn))
			}
			if p.Get(from+b.Left) == ownPawn &&
				!isPinned(p, from+b.Left, king) {
				moves.Append(pos.NewEnPassant(from+b.Left, p.EnPassantTarget()),
					mvvLvaSortValue(pos.Pawn, pos.Pawn))
			}
		}
	} else {
		//
		// slider attack
		//
		if attacks == 2 {
			// double check, only king moves possible
			// as we have 2 attack directions the king may not walk along
			// we do it the dirty and save way, as this is a rare case anyway
			// todo: it would be safe to just remove the king and check all directions
			// but this is slower, which is an indication how slow removing
			// and resetting a piece in the piece list is
			for _, direction := range movements[pos.King].Directions {
				color := p.Get(king + direction).Color()
				if color == pos.NoColor || color == opponent {
					move := pos.NewMove(king, king+direction)
					uMove := pos.MakeMove(p, move)
					if !UnderAttack(p, king+direction, opponent) {
						moves.Append(move,
							mvvLvaSortValue(p.Get(king+direction).Type(), pos.King))
					}
					pos.UnmakeMove(p, uMove)
				}
			}
			return
		}
		attackDirection := moveInfo(king, from).Direction()
		// king evasions
		for _, direction := range movements[pos.King].Directions {
			switch {
			// king can capture - no need to worry about walking along the attack ray
			case king+direction == from &&
				!UnderAttack(p, king+direction, opponent):
				moves.Append(pos.NewMove(king, king+direction),
					mvvLvaSortValue(p.Get(king+direction).Type(), pos.King))
				// non captures
			case p.Get(king+direction) == pos.NoPiece &&
				// dont walk along the attack ray
				direction != attackDirection && direction != -attackDirection &&
				!UnderAttack(p, king+direction, opponent):
				moves.Append(pos.NewMove(king, king+direction), mvvLvaSortValue(pos.NoPieceType, pos.King))
				// captures
			case p.Get(king+direction).Color() == opponent &&
				// dont walk along the attack ray
				direction != -attackDirection && direction != attackDirection &&
				!UnderAttack(p, king+direction, opponent):
				moves.Append(pos.NewMove(king, king+direction),
					mvvLvaSortValue(p.Get(king+direction).Type(), pos.King))
			}
		}
		// myVector := maxRay(p, stm)
		// try to block the attack ray or capture the attacker
		for rayBlock := from; rayBlock != king; rayBlock -= attackDirection {
			if rayBlock != from {
				// try to block the ray with a pawn push, pawn captures are handled below
				if p.Get(rayBlock+down) == ownPawn && !isPinned(p, rayBlock+down, king) { // testit
					if b.GetRank(rayBlock+down) == pos.PromotionRank(stm) {
						// pawn promotions to block attack ray
						for promotion := pos.Queen; promotion != pos.Pawn; promotion-- {
							moves.Append(pos.NewPromotion(rayBlock+down, rayBlock, promotion),
								mvvLvaSortValue(promotion, pos.Pawn))
						}
					} else {
						moves.Append(pos.NewMove(rayBlock+down, rayBlock),
							mvvLvaSortValue(pos.NoPieceType, pos.Pawn))
					}
				} else {
					// see if we can make a double push to block the ray
					if b.GetRank(rayBlock+down<<1) == pos.PawnRank(stm) &&
						p.Get(rayBlock+down<<1) == ownPawn &&
						p.Get(rayBlock+down) == pos.NoPiece &&
						!isPinned(p, rayBlock+down<<1, king) { // testit
						moves.Append(pos.NewMove(rayBlock+down<<1, rayBlock),
							mvvLvaSortValue(pos.NoPieceType, pos.Pawn))
					}
				}
			}
			// slider captures/blocks
			for _, direction := range movements[pos.Queen].Directions {
				square := rayBlock + direction
				for ; p.Get(square) == pos.NoPiece; square += direction {
				}
				if p.Get(square).Color() == stm &&
					// king moves see code above
					p.Get(square).Type() != pos.King &&
					// pawn moves see code above
					p.Get(square).Type() != pos.Pawn &&
					moveInfo(square, rayBlock).canMove(p.Get(square).Type()) &&
					!isPinned(p, square, king) { // testit
					moves.Append(pos.NewMove(square, rayBlock),
						mvvLvaSortValue(p.Get(rayBlock).Type(), p.Get(square).Type()))
				}
			}
			// knight captures/blocks
			for _, direction := range movements[pos.Knight].Directions {
				square := rayBlock + direction
				if p.Get(square) == ownKnight && !isPinned(p, square, king) {
					moves.Append(pos.NewMove(square, rayBlock),
						mvvLvaSortValue(p.Get(rayBlock).Type(), pos.Knight))
				}
			}
		}
	}
	downLeft := from + down + b.Left
	// capture attacker with a pawn
	// don:t forget: promotion evasions not possible when attacker is a pawn
	promote := b.GetRank(from+down) == pos.PromotionRank(stm)
	if p.Get(downLeft) == ownPawn && !isPinned(p, downLeft, king) { // testit
		if promote {
			for promotion := pos.Queen; promotion != pos.Pawn; promotion-- {
				moves.Append(pos.NewPromotion(downLeft, from, promotion),
					mvvLvaSortValue(promotion, pos.Pawn))
			}
		} else {
			moves.Append(pos.NewMove(downLeft, from),
				mvvLvaSortValue(p.Get(from).Type(), pos.Pawn))
		}
	}
	downRight := from + down + b.Right
	if p.Get(downRight) == ownPawn && !isPinned(p, downRight, king) { // testit{
		if promote {
			for promotion := pos.Queen; promotion != pos.Pawn; promotion-- {
				moves.Append(pos.NewPromotion(downRight, from, promotion),
					mvvLvaSortValue(promotion, pos.Pawn))
			}
		} else {
			moves.Append(pos.NewMove(downRight, from),
				mvvLvaSortValue(p.Get(from).Type(), pos.Pawn))
		}
	}
	// en passant blocks attack -> impossible
}

// IsQuietPseudoLegalMove returns true if move
// is a valid quiet pseudo legal move for this position.
func IsQuietPseudoLegalMove(p *pos.Position, move pos.Move) bool {
	Stats.IsQuietPseudoLegalMove++

	if p.Get(move.Start()).Color() != p.SideToMove() {
		// wrong color of piece to move
		return false
	}
	if p.Get(move.Destination()) != pos.NoPiece {
		// destination occupied
		return false
	}
	if move.Castling() != pos.NoCastling {
		return p.CastlingRights().Possible(p.SideToMove(), move.Castling()) &&
			castlingTemporaryPossible(p, move.Castling())
	}
	if p.Get(move.Start()).Type() == pos.Pawn {
		forward := pos.MoveDirection(p.SideToMove())
		// one step forward is ok, but not to the promotion rank
		if move.Destination()-move.Start() == forward &&
			b.GetRank(move.Start()) != pos.PromotionRank(p.SideToMove()) {
			return true
		}
		// double push from pawn rank and crossed square is empty?
		return move.Destination()-move.Start() == forward<<1 &&
			b.GetRank(move.Start()) == pos.PawnRank(p.SideToMove()) &&
			p.Get(move.Start()+forward) == pos.NoPiece
	}
	// Now we know that
	// - we have our own piece on the start square
	// - the destination square is empty
	// - we have no castling, no promotion and no pawn push
	if !moveInfo(move.Start(), move.Destination()).canMove(p.Get(move.Start()).Type()) {
		// piece on start square can't move to destination square
		return false
	}
	direction := moveInfo(move.Start(), move.Destination()).Direction()
	square := move.Start() + direction
	for ; square != move.Destination() && p.Get(square) == pos.NoPiece; square += direction {
	}
	return square == move.Destination()
}

// IsLegalMoveWhileNotInCheck returns true if the pseudo legal move is a legal move.
// TODO: rewrite en passant code
func IsLegalMoveWhileNotInCheck(p *pos.Position, move pos.Move) bool {
	Stats.IsLegalMoveWhileNotInCheck++
	if move.Castling() != pos.NoCastling {
		// castlings are legal moves
		return true
	}
	Stats.IsLegalMoveWhileNotInCheck1++

	opponent := pos.Opponent(p.SideToMove())
	start, dst := move.Start(), move.Destination()
	king := p.King(p.SideToMove())

	if move.CapturesEnPassant() {
		// if a pawn will capture a pawn en passant, check if there is an x-ray attack
		// through the captured pawn
		enPassantVictim := p.EnPassantTarget() + pos.MoveDirection(opponent)
		if isPinned(p, enPassantVictim, king) {
			// ok, the enemy pawn was pinned, so capturing it leaves our king in check?
			// no, the capturing pawn can move itself between pawn and attacker, see
			// "8/8/8/6k1/5pP1/8/8/4K1R1 b - g3 0 1" - exg3 is legal!
			pinDirection := moveInfo(king, enPassantVictim).Direction()
			if enPassantVictim+pinDirection != move.Destination() && enPassantVictim-pinDirection != move.Destination() {
				return false
			}
		}
		// nasty case: capturing and captured pawn both (!) block the rank for an attacker to the king
		// only possible if attacker (rook or queen) and king are on the same ranks.
		// That means the own pawn is not pinned yet! lets ignore them when searching for an attacker.
		pinDirection := moveInfo(king, enPassantVictim).Direction()
		square := king + pinDirection
		for ; square == start || square == enPassantVictim || p.Get(square) == pos.NoPiece; square += pinDirection {
			// this is necessary due to "8/8/8/6k1/5pP1/8/8/4K1R1 b - g3 0 1" (see above)
			// we need to rewrite this whole thing
			if square == move.Destination() {
				break
			}
		}
		if p.Get(square).Color() == opponent &&
			moveInfo(king, square).canMove(p.Get(square).Type()) {
			// we found an attacker behind both pawns
			return false
		}
	}
	Stats.IsLegalMoveWhileNotInCheck2++
	if start == king {
		return !UnderAttack(p, move.Destination(), opponent)
	}
	Stats.IsLegalMoveWhileNotInCheck3++

	attackRay := moveInfo(king, start)
	if !attackRay.isRay() {
		// mover can't be pinned
		return true
	}
	Stats.IsLegalMoveWhileNotInCheck4++
	if attackRay.Direction() == moveInfo(king, dst).Direction() {
		// piece walks along the ray from the king to a potential attacker
		return true
	}
	Stats.IsLegalMoveWhileNotInCheck5++
	// check if there is an attacker behind the piece
	dir := attackRay.Direction()
	var sq b.Square
	for sq = king + dir; p.Get(sq) == pos.NoPiece || sq == start; sq += dir {
	}
	if p.Get(sq).Color() == opponent &&
		moveInfo(king, sq).canMove(p.Get(sq).Type()) {
		return false
	}
	Stats.IsLegalMoveWhileNotInCheck6++
	return true
}

func isPinned(p *pos.Position, pinCandidate, king b.Square) bool {
	Stats.IsPinned++
	var square b.Square

	// check if slider attack possible
	if !moveInfo(king, pinCandidate).isRay() {
		// no ray from pinCandidate to king -> not pinned
		return false
	}
	Stats.IsPinned1++
	// check if an attacker is behind the pin candidate
	direction := moveInfo(king, pinCandidate).Direction()
	for square = pinCandidate + direction; p.Get(square) == pos.NoPiece; square += direction {
	}
	if p.Get(square).Color() != pos.Opponent(p.SideToMove()) ||
		!moveInfo(king, square).canMove(p.Get(square).Type()) {
		// no attacker found -> not pinned
		return false
	}
	Stats.IsPinned2++
	// check if there is a blocker between king and pin candidate
	for square = king + direction; p.Get(square) == pos.NoPiece; square += direction {
	}
	return square == pinCandidate
}

var pseudoLegal = new(Movelist)

func GenerateLegalMoves(p *pos.Position) []pos.Move {
	pseudoLegal.Clear()
	GenerateMoves(p, pseudoLegal)
	stm := p.SideToMove()
	var legalMoves []pos.Move
	for move := pseudoLegal.Next(); move != pos.NoMove; move = pseudoLegal.Next() {
		uMove := pos.MakeMove(p, move)
		if !IsCheck(p, stm) {
			legalMoves = append(legalMoves, move)
		}
		pos.UnmakeMove(p, uMove)
	}
	return legalMoves
}

// only if stm is not in check!
func GenerateSortedLegalMoves(p *pos.Position, legal *Movelist) {
	pseudoLegal.Clear()
	GenerateMoves(p, pseudoLegal)
	pseudoLegal.Sort()
	for move := pseudoLegal.Next(); move != pos.NoMove; move = pseudoLegal.Next() {
		if IsLegalMoveWhileNotInCheck(p, move) {
			legal.Append(move, 0)
		}
	}
}

// GenerateMoves generates all pseudo legal moves of a position.
func GenerateMoves(p *pos.Position, moves *Movelist) {
	GeneratePawnCapturesAndPromotions(p, moves)
	GeneratePieceCaptures(p, moves)
	GeneratePieceMoves(p, moves)
	GeneratePawnMoves(p, moves)
	GenerateCastlings(p, moves)
}
